{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "从PDF文件获取表格中的数据，也是日常办公容易涉及到的一项工作。一个一个复制吧，效率确实太低了。用Python从PDF文档中提取表格数据，并写入Excel文件，灰常灰常高效。上市公司的年报往往包含几百张表格，用它作为例子再合适不过，搞定这个，其他含表格的PDF都是小儿科了。今天以\"保利地产年报\"为例，这个PDF文档中有321页含有表格，总表格数超过这个数了。\n",
    "<br/>\n",
    "<br/>先导入PDF读取模块`pdfplumber`，随便挑一页看下表格数据的结构。如下，我们挑了第4页`pages[3]`来读取其中的表格，并显示。这里读取表格，用到了`extract_tables()`，即默认每页有多个表格。它会将单个表格的数据按行读取存入列表，再将每个表格的所有数据汇总存到一个上一级列表，最后将所有表格的数据汇总到一个大列表。而`extract_table()`方法则只能读一张表，当一个页面有多张表，就默认选第一个，因此会漏掉后面的。而且它们的数据结构也不同，差异如下。\n",
    "![](images\\data_s.png)\n",
    "<br/>\n",
    "<br/>“保利地产年报”第四页如图所示，读取的结果存到列表`table`，显示如下。\n",
    "![](images\\p4.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[['', '常用词语释义', None, None, None, None, None, ''], ['中国证监会', None, '', '指', '', '', '中国证券监督管理委员会', ''], ['国资委', None, '', '指', '', '', '国务院国有资产监督管理委员会', ''], ['上交所', None, '', '指', '', '上海证券交易所', None, None], ['公司、本公司、保利地产', None, '指', None, None, '保利发展控股集团股份有限公司，原名称保利房\\n地产（集团）股份有限公司', None, None], ['报告期、本报告期', None, '', '指', '', '2018年度', None, None], ['元、万元、亿元', None, '', '指', '', '人民币元、人民币万元、人民币亿元', None, None]], [['公司的中文名称', '保利发展控股集团股份有限公司'], ['公司的中文简称', '保利地产'], ['公司的外文名称', 'Poly Developments and Holdings Group Co., Ltd.'], ['公司的外文名称缩写', 'PDH'], ['公司的法定代表人', '宋广菊']], [['', '董事会秘书', '证券事务代表'], ['姓名', '黄海', '尹超'], ['联系地址', '广东省广州市海珠区阅江中路688号保利国际广场北塔33层董事会办公室', None], ['电话', '020-89898833', None], ['传真', '020-89898666-8831', None], ['电子信箱', 'stock@polycn.com', None]], [['公司注册地址', '广州市海珠区阅江中路688号保利国际广场30-33层'], ['公司注册地址的邮政编码', '510308'], ['公司办公地址', '广州市海珠区阅江中路688号保利国际广场北塔30-33层'], ['公司办公地址的邮政编码', '510308'], ['公司网址', 'www.polycn.com；www.gzpoly.com'], ['电子信箱', 'stock@polycn.com']], [['公司选定的信息披露媒体名称', '《中国证券报》、《上海证券报》、《证券时报》'], ['登载年度报告的中国证监会指定网站的网址', 'www.sse.com.cn'], ['公司年度报告备置地点', '公司董事会办公室']]]\n"
     ]
    }
   ],
   "source": [
    "#观察读取出来的表格的数据结构\n",
    "import pdfplumber\n",
    "with pdfplumber.open(\"保利地产年报.pdf\") as p:\n",
    "    page = p.pages[3] #选取第4页(起始页为0)\n",
    "    table = page.extract_tables() #多表格读取，存为嵌套列表\n",
    "    print(table)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "确保可正常读取表格，以及了解读取出来的表格的数据结构，下面就可以一次性读取出所有表格，并存入Excel文件中了。导入相应模块，然后使用`pdfplumber`打开PDF文件。使用`Workbook()`新建Excel工作簿，然后使用`remove()`将其自带的工作表删除。因为我们想用PDF文件中表格所在的页码给相应的Excel工作表命名，以便二者的编号一致，方便后续查询。所以需要使用`enumerate()`给PDF的页从1开始编号。然后使用`extract_tables()`获取表格数据。\n",
    "<br/>\n",
    "<br/>当然，如果当页没有表格，则`extract_tables()`获得的是空值`None`。在后续的操作中，空值会报错，所以加入`if`语句来做个判断。只有当列表`tables`不为空，即里面有货的时候，才建新的Excel表格，并执行后续的写入操作。列表`tables`若为空（即当页没有表格），则直接跳到下一页。\n",
    "<br/>\n",
    "<br/>当发现当页有表格后，新建一个Excel表，以“Sheet”加上此时PDF的页码（比如“Sheet3”）命名。在写入数据时，先用一个`for`循环获得单个表格的数据，再用第二个`for`循环获得表格中一行的数据，然后写入Excel表。最后保存数据。由于表格太多，程序运行时间较长，大约需要3分钟。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "import pdfplumber\n",
    "from openpyxl import Workbook    \n",
    "with pdfplumber.open(\"保利地产年报.pdf\") as p:\n",
    "    wb = Workbook() #新建excel工作簿\n",
    "    wb.remove(wb.worksheets[0])#删除工作簿自带的工作表\n",
    "    for index,page in enumerate(p.pages,start = 1): #从1开始给所有页编号\n",
    "        tables = page.extract_tables() #读取表格\n",
    "        if tables: #判断是否存在表格，若不存在，则不执行下面的语句\n",
    "            ws = wb.create_sheet(f\"Sheet{index}\") #新建工作表，表名的编号与表在PDF中的页码一致\n",
    "            for table in tables: #遍历所有列表\n",
    "                for row in table: #遍历列表中的所有子列表，里面保存着行数据\n",
    "                    ws.append(row) #写入excel表\n",
    "    wb.save(\"保利地产年报表格.xlsx\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "数百个表格就这样潇洒地复制到Excel表格中了。\n",
    "![](images\\\\result.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果想要指定某个表格，在提取数据的时候指定页码即可。但如果想批量导出大量不同公司的年报的指定表格，则需要使用关键词定位了。还好，无论深圳市场还是上海市场，公司的年报中的标题基本都是唯一的，这给我们用标题做关键词提供了方便。假设我们需要提取公司“主要会计数据”下面的表格，则用关键词“主要会计数据”定位即可。如下以此为例进行操作。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import pdfplumber\n",
    "from openpyxl import Workbook    \n",
    "\n",
    "path='PDF'  #文件所在文件夹\n",
    "files = [path+\"\\\\\"+i for i in os.listdir(path)] #获取文件夹下的文件名,并拼接完整路径\n",
    "key_words = \"主要会计数据\"\n",
    "\n",
    "for file in files:\n",
    "    with pdfplumber.open(file) as p:\n",
    "        wb = Workbook() #新建excel工作簿\n",
    "        wb.remove(wb.worksheets[0])#删除工作簿自带的工作表\n",
    "        \n",
    "        #获取关键词所在页及下一页的页码\n",
    "        pages_wanted = []\n",
    "        for index,page in enumerate(p.pages): #从0开始给所有页编号\n",
    "            if key_words in page.extract_text():\n",
    "                pages_wanted.append(index)\n",
    "                pages_wanted.append(index+1)\n",
    "                break\n",
    "        \n",
    "        #提取指定页码里的表格\n",
    "        for i in pages_wanted:     \n",
    "            page = p.pages[i]\n",
    "            tables = page.extract_tables() #读取表格\n",
    "            if tables: #判断是否存在表格，若不存在，则不执行下面的语句\n",
    "                ws = wb.create_sheet(f\"Sheet{i+1}\") #新建工作表，表名的编号与表在PDF中的页码一致\n",
    "                for table in tables: #遍历所有列表\n",
    "                    for row in table: #遍历列表中的所有子列表，里面保存着行数据\n",
    "                        ws.append(row) #写入excel表\n",
    "        wb.save(\"Excel\\\\{}.xlsx\".format(file.split(\"\\\\\")[1].split(\".\")[0]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "以上，增加了一段获取关键词所在页码及下一页的页码的程序。之所以要获取关键词下一页页码，是因为有些表格会跨页，为了不遗漏数据，宁愿多获取一点。一旦找到关键词所在页，马上用`break`停止`for`循环。后面再遍历`pages_wanted`里面储存的页码，提取表格并写入Excel文件，并保存即可。批量获取的指定内容保存在`Excel`文件夹下。\n",
    "![](images\\result1.png)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
